package com.jason.library.asm;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.List;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.MethodNode;

/**
 * @author ChenBaoyang
 * @description: AsmSample
 * @date 2021/11/23 15:53
 */
public class AsmSample {


    public static void main(String[] args) {
        try {
            visitUserFieldAndMethod2();
            //copyClass();
            insertFieldCode();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * 获取 User.class 中包含的所有方法以及字段：
     * @throws Exception
     */
    public static void visitUserFieldAndMethod() throws Exception {
        Class clz = User.class;
        String clzFilePath = Utils.getClassFilePath(clz);
        ClassReader classReader = new ClassReader(new FileInputStream(clzFilePath));
        ClassNode classNode = new ClassNode(Opcodes.ASM5);
        classReader.accept(classNode, 0);

        List<MethodNode> methodNodes = classNode.methods;
        List<FieldNode> fieldNodes = classNode.fields;


        System.out.println("methods:");
        for (MethodNode methodNode : methodNodes) {
            System.out.println(methodNode.name + ", " + methodNode.desc);
        }

        System.out.println("fields:");
        for (FieldNode fieldNode : fieldNodes) {
            System.out.println(fieldNode.name + ", " + fieldNode.desc);
        }
    }

    /**
     * 获取 User.class 中包含的所有方法以及字段：等同于 {@linkplain #visitUserFieldAndMethod()}
     * @throws Exception
     */
    public static void visitUserFieldAndMethod2() throws Exception {
        Class clz = User.class;
        String clzFilePath = Utils.getClassFilePath(clz);
        ClassReader classReader = new ClassReader(new FileInputStream(clzFilePath));
        ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM5) {
            @Override
            public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
                System.out.println("visit field:" + name + " , desc = " + descriptor);
                return super.visitField(access, name, descriptor, signature, value);
            }

            @Override
            public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
                System.out.println("visit method:" + name + " , desc = " + descriptor);
                return super.visitMethod(access, name, descriptor, signature, exceptions);
            }
        };
        classReader.accept(classVisitor, 0);
    }


    /**
     * 复制一个class文件
     */
    public static void copyClass() throws Exception{
        Class clz = Tool.class;
        String clzFilePath = Utils.getClassFilePath(clz);
        ClassReader classReader = new ClassReader(new FileInputStream(clzFilePath));

        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        classReader.accept(classWriter,0);

        //写入文件
        byte[] bytes = classWriter.toByteArray();
        FileOutputStream outputStream = new FileOutputStream("/Users/chenbaoyang/copyed.class");
        outputStream.write(bytes);
        outputStream.flush();
        outputStream.close();
    }

    /**
     * 开始尝试添加字段
     */
    public static void insertFieldCode() throws Exception {
        Class clz = Tool.class;
        String clzFilePath = Utils.getClassFilePath(clz);
        ClassReader classReader = new ClassReader(new FileInputStream(clzFilePath));

        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);

        AddTimerClassVisitor visitor = new AddTimerClassVisitor(Opcodes.ASM5, classWriter);
        classReader.accept(visitor, 0);

        //写入文件(修改原文件)
        byte[] bytes = classWriter.toByteArray();
        FileOutputStream outputStream = new FileOutputStream(clzFilePath);
        outputStream.write(bytes);
        outputStream.flush();
        outputStream.close();
    }


}
